package hikari.gui.animationeditor;

import com.gooddies.events.BooleanEvent;
import com.gooddies.events.ValueChangedEvent;
import com.gooddies.graphics.InteractiveIcon;
import com.gooddies.graphics.LinkIconLabel;
import com.gooddies.swing.HSlider;
import com.gooddies.swing.HSlider.SelectedMark;
import com.gooddies.swing.HToggleButton;
import com.gooddies.swing.VerticalLayout;
import com.gooddies.texteditors.*;
import com.gooddies.wiring.Wiring;
import hikari.gui.assets.AssetsTree;
import hikari.gui.guicomponents.hLabel;
import hikari.gui.panels.Main3dPanel;
import hikari.instruments.CameraObserveInstrument;
import hikari.instruments.HikariInstrument;
import hikari.managers.AnimationSettingStore;
import hikari.objects3d.helper_objects.ExtendedEditorCamera;
import hikari.objects_data.Model3dData;
import hikari.utils.IRenderable;
import hikari.utils.Utils;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import net.miginfocom.swing.MigLayout;
import org.swig.jirr.*;

/**
 * @author sad
 */
public class AnimationEditorWindow extends JDialog implements IRenderable {

    private Model3dData data;
    private String path;
    private Main3dPanel drawPanel;
    private IrrlichtDevice device;
    private hLabel modelName;
    private DefaultFloatTextField framerate;
    private DefaultIntegerTextField startFrame;
    private DefaultIntegerTextField endFrame;
    private DefaultIntegerTextField currentFrame;
    private Timer renderingTimer;
    private hLabel totalFramesCountLabel;
    private IAnimatedMeshSceneNode model;
    private ExtendedEditorCamera camera;
    private int frameIndex = 0;
    private boolean enablehandlers = true;
    private JPanel animSeqsPanel = null;
    private ArrayList<AnimationSequencePanel> sequences = new ArrayList<>();
    private AnimationSequencePanel selectedSequencePanel;
    private GraphicDrawer_AnimationDiff graph_diff_drawer;
    private boolean renderStop = false;

    public AnimationEditorWindow(Frame owner, String pathToModel) {
        super(owner);
        setTitle("Animation editing");
        path = pathToModel;
        data = Wiring.getComponent(AnimationSettingStore.class).getDataForAnimationModel(pathToModel);
        init();
    }

    private void init() {
        setLayout(new MigLayout());
        setModal(true);
        drawPanel = Wiring.getComponent(Main3dPanel.class);
        drawPanel.setPreferredSize(new Dimension(400, 300));
        add(drawPanel, "w 200!, h 200!");
        add(createInfoPanel(), "top,wrap");
        JScrollPane scroll = new JScrollPane();
        scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        scroll.setPreferredSize(new Dimension(420, 300));
        animSeqsPanel = new JPanel();
        animSeqsPanel.setLayout(new VerticalLayout().setStartGap(0).setSideGap(0));
        scroll.getViewport().add(animSeqsPanel);
        add(scroll, "spanx 10, wrap");
        JPanel okCancelButtonsPanel = new JPanel();
        okCancelButtonsPanel.setLayout(new FlowLayout());
        JButton okButton = new JButton("OK");
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onOk();
            }
        });

        JButton cancelButton = new JButton("Cancel");
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });

        okCancelButtonsPanel.add(okButton);
        okCancelButtonsPanel.add(cancelButton);
        add(okCancelButtonsPanel, "spanx 10, wrap");
        graph_diff_drawer = new GraphicDrawer_AnimationDiff();
        graph_diff_drawer.setPreferredSize(new Dimension(10, 50));
        graph_diff_drawer.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    recalculateAnimationData();
                }
            }
        });

        //add(graph_diff_drawer, "spanx 10, wrap");
        //add(new AnimationSequencePanel(),"spanx 10");
        initIrrlichtDevice();
        pack();
        setLocationRelativeTo(null);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                renderingTimer.stop();
                device.closeDevice();
                setVisible(false);
                dispose();
            }
        });
    }

    private void loadAnimData(Model3dData data) {
        for (String trackName : data.getAnimationNames()) {
            Model3dData.AnimationTrackData track = data.getTrackData(trackName);
            AnimationSequencePanel sequence = addNewAnimSeq();
            sequence.setAnimSeqCurrentFrame(track.getStartFrame());
            sequence.setAnimSeqStartFrame(track.getStartFrame());
            sequence.setAnimSeqEndFrame(track.getEndFrame());
            sequence.setAnimSeqAnimationSpeed(track.getAnimationSpeed());
            sequence.setTrackName(trackName);
        }
    }

    private void onOk() {
        data.clearAnimationData();
        for (AnimationSequencePanel panel : sequences) {
            data.putAnimationTrack(
                    panel.getTrackName(),
                    panel.getAnimSeqStartFrame(),
                    panel.getAnimSeqEndFrame(),
                    panel.getAnimSeqAnimationSpeed());
        }

        Wiring.getComponent(AnimationSettingStore.class).putDataForAnimationModel(path, data);
        closeWindow();
    }

    private void onCancel() {
        closeWindow();
    }

    private void closeWindow() {
        setVisible(false);
        dispose();
    }

    private void recalculateAnimationData() {
        renderStop = true;
        final JDialog dialog = new JDialog(this);
        try {
            dialog.setModal(true);
            dialog.setUndecorated(true);
            dialog.setLayout(new VerticalLayout());
            dialog.setSize(200, 100);
            JLabel label = new JLabel("Please wait while data calculates");
            final JLabel percentLabel = new JLabel("0%");
            percentLabel.setPreferredSize(new Dimension(50, 20));

            dialog.add(label);
            dialog.add(percentLabel);
            dialog.addWindowListener(new WindowAdapter() {
                @Override
                public void windowActivated(WindowEvent e) {
                    SwingWorker thread = new SwingWorker() {
                        @Override
                        protected Object doInBackground() throws Exception {
                            AnimationDiffRecounter recounter = Wiring.getComponent(AnimationDiffRecounter.class);
                            double[] animationData = recounter.recount(model, getEditorCurrentFrame(), device, new AnimationDiffRecounter.OnProcessFrame() {
                                private int lastFrame = 0;

                                @Override
                                public void frameProcessed(int frame, int totalFrames) {
                                    final int framePercent = (int) ((double) frame / (double) totalFrames * 100);
                                    if (framePercent != lastFrame) {
                                        lastFrame = framePercent;
                                        SwingUtilities.invokeLater(new Runnable() {
                                            @Override
                                            public void run() {
                                                percentLabel.setText(String.format("%d%%", framePercent));
                                            }
                                        });

                                    }
                                }
                            });
                            return animationData;
                        }

                        @Override
                        protected void done() {
                            try {
                                double[] animationData = (double[]) get();
                                graph_diff_drawer.setData(animationData);
                                graph_diff_drawer.repaint();
                                dialog.setVisible(false);
                            } catch (InterruptedException | ExecutionException ex) {
                                Logger.getLogger(AnimationEditorWindow.class.getName()).log(Level.SEVERE, null, ex);
                            }

                        }
                    };
                    thread.execute();
                }
            });
            dialog.setLocationRelativeTo(null);
            dialog.setVisible(true);
        } finally {
            dialog.setVisible(false);
            dialog.dispose();
            renderStop = false;
        }
    }

    private JPanel createInfoPanel() {
        JPanel panel = new JPanel(new MigLayout());
        panel.add(new hLabel("Model path:"));
        modelName = new hLabel("");
        panel.add(modelName, "wrap");

        panel.add(new hLabel("Frame count:"));
        totalFramesCountLabel = new hLabel("");
        panel.add(totalFramesCountLabel, "wrap");

        panel.add(new hLabel("Frame rate:"));
        framerate = new DefaultFloatTextField(0, 9999999);
        framerate.setShowGroupSymbol(false);
        framerate.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
            @Override
            public void valueChanged(Float value) {
                if (enablehandlers) {
                    setFrameRateChanged(value);
                }
            }
        });
        panel.add(framerate, "w 50!,wrap");

        panel.add(new hLabel("Start frame:"));
        startFrame = new DefaultIntegerTextField(0, 9999999);
        startFrame.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
            @Override
            protected void valueChanged(Float value) {
                if (enablehandlers) {
                    int frame = (int) (float) value;
                    setStartFrame(frame);
                    if (selectedSequencePanel != null) {
                        selectedSequencePanel.setAnimSeqStartFrame(frame);
                    }

                }
            }
        });
        panel.add(startFrame, "w 50!,wrap");

        panel.add(new hLabel("End frame:"));
        endFrame = new DefaultIntegerTextField(0, 9999999);
        endFrame.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
            @Override
            public void valueChanged(Float value) {
                if (enablehandlers) {
                    int frame = (int) (float) value;
                    setEndFrame(frame);
                    if (selectedSequencePanel != null) {
                        selectedSequencePanel.setAnimSeqEndFrame(frame);
                    }
                }
            }
        });
        panel.add(endFrame, "w 50!,wrap");

        panel.add(new hLabel("Current frame:"));
        currentFrame = new DefaultIntegerTextField(0, 9999999);
        panel.add(currentFrame, "w 50!,wrap");
        currentFrame.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
            @Override
            public void valueChanged(Float value) {
                if (enablehandlers) {
                    int frame = (int) (float) value;
                    setCurrentFrame(frame);
                }
            }
        });

        int toggleGroup = "AnimationEditorWindow".hashCode();
        HToggleButton rewind = new HToggleButton(Wiring.getComponent(Utils.class).loadImageFromResources("icons/rewind.png")).setToggleGroup(toggleGroup);
        rewind.setOnSelect(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (enablehandlers) {
                    animationRewind();
                }
            }
        });
        HToggleButton toEnd = new HToggleButton(Wiring.getComponent(Utils.class).loadImageFromResources("icons/toEnd.png")).setToggleGroup(toggleGroup);
        toEnd.setOnSelect(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (enablehandlers) {
                    animationToEnd();
                }
            }
        });
        HToggleButton play = new HToggleButton(Wiring.getComponent(Utils.class).loadImageFromResources("icons/play.png")).setToggleGroup(toggleGroup);
        play.setOnSelect(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (enablehandlers) {
                    animationPlay();
                }
            }
        });

        HToggleButton pause = new HToggleButton(Wiring.getComponent(Utils.class).loadImageFromResources("icons/pause.png")).setToggleGroup(toggleGroup);
        pause.setOnSelect(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (enablehandlers) {
                    animationPause();
                }
            }
        });

        JPanel buttonPanel = new JPanel(new FlowLayout());
        buttonPanel.add(rewind);
        buttonPanel.add(play);
        buttonPanel.add(pause);
        buttonPanel.add(toEnd);

        panel.add(buttonPanel, "spanx 10, wrap");
        JButton addNewAnimSeq = new JButton("Add seq");
        addNewAnimSeq.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                addNewAnimSeq();
            }
        });
        panel.add(addNewAnimSeq);
        return panel;
    }

    private AnimationSequencePanel addNewAnimSeq() {
        AnimationSequencePanel newSeq = new AnimationSequencePanel((int) model.getMesh().getFrameCount(), model.getMesh().getAnimationSpeed());
        animSeqsPanel.add(newSeq);
        sequences.add(newSeq);
        invalidate();
        validate();
        repaint();
        return newSeq;
    }

    public void disableHandlers() {
        enablehandlers = false;
    }

    public void enableHandlers() {
        enablehandlers = true;
    }

    public void setFrameRateChanged(float speed) {
        model.setAnimationSpeed(speed);
    }

    public void setStartFrame(int frame) {
        int tEndFrame = model.getEndFrame();
        if (frame > tEndFrame) {
            tEndFrame = frame;
        }
        model.setFrameLoop(frame, tEndFrame);
    }

    public void setEndFrame(int frame) {
        model.setFrameLoop(model.getStartFrame(), frame);
    }

    public void setCurrentFrame(int frame) {
        model.setCurrentFrame(frame);
    }

    private int getEditorStartFrame() {
        return startFrame.getValue();
    }

    private void setEditorStartFrame(int frame) {
        startFrame.setValue(frame);
    }

    private void setEditorEndFrame(int frame) {
        endFrame.setValue(frame);
    }

    private int getEditorEndFrame() {
        return endFrame.getValue();
    }

    private int getEditorCurrentFrame() {
        return currentFrame.getValue();
    }

    private void setEditorCurrentFrame(int frame) {
        currentFrame.setValue(frame);
    }

    private float getEditorFrameRate() {
        return (float) framerate.getValue();
    }

    private void setEditorFrameRate(float value) {
        framerate.setValue(value);
    }

    private void animationRewind() {
        disableHandlers();
        model.setAnimationSpeed(0);
        int frame = getEditorStartFrame();
        model.setCurrentFrame(frame);
        setEditorCurrentFrame(frame);
        if (selectedSequencePanel != null) {
            selectedSequencePanel.setAnimSeqCurrentFrame(frame);
        }
        enableHandlers();
    }

    private void animationToEnd() {
        disableHandlers();
        model.setAnimationSpeed(0);
        int frame = getEditorEndFrame();
        model.setCurrentFrame(frame);
        setEditorCurrentFrame(frame);
        if (selectedSequencePanel != null) {
            selectedSequencePanel.setAnimSeqCurrentFrame(frame);
        }
        enableHandlers();
    }

    private void animationPlay() {
        model.setAnimationSpeed(getEditorFrameRate());
    }

    private void animationPause() {
        model.setAnimationSpeed(0);
    }

    private void initIrrlichtDevice() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                SIrrlichtCreationParameters params = new SIrrlichtCreationParameters();
                params.setAntiAlias((short) 0);
                params.setBits((short) 32);
                params.setDriverType(E_DRIVER_TYPE.EDT_DIRECT3D9);
                params.setFullscreen(false);
                params.setIgnoreInput(true);
                params.setStencilbuffer(true);
                params.setVsync(false);
                params.setWindowId(drawPanel.getHWND());
                device = Jirr.createDeviceEx(params);
                ISceneManager sceneManager = device.getSceneManager();
                camera = new ExtendedEditorCamera(sceneManager.addEmptySceneNode(), sceneManager);
                camera.setPosition(new vector3df(0, 0, -100));

                loadData();
                HikariInstrument instrument = Wiring.getComponent(CameraObserveInstrument.class).setCamera(camera).setRenderSurface(AnimationEditorWindow.this);
                drawPanel.setInstrument(instrument);

                renderingTimer = new Timer(1, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (renderStop) {
                            return;
                        }
                        render();
                        if (model.getAnimationSpeed() != 0) {
                            if (selectedSequencePanel != null) {
                                selectedSequencePanel.setAnimSeqCurrentFrame((int) model.getFrameNr());
                            }
                        }
                    }
                });

                renderingTimer.setRepeats(true);
                renderingTimer.start();
                loadAnimData(data);
            }
        });
    }

    @Override
    public void render() {
        device.run();
        camera.updateCameraTarget();
        IVideoDriver driver = device.getVideoDriver();
        driver.beginScene(true, true, SColor.BLUE_L);
        device.getSceneManager().drawAll();
        driver.endScene();
        frameIndex++;
        if ((frameIndex % 5) == 0) {
            if (model.getAnimationSpeed() != 0) {
                setEditorCurrentFrame((int) model.getFrameNr());
            }
        }
    }

    private void loadData() {
        ISceneManager sm = device.getSceneManager();
        IAnimatedMesh mesh = sm.getMesh(Wiring.getComponent(AssetsTree.class).getAbsoluteAssetPathFromRelativePath(path));
        model = sm.addAnimatedMeshSceneNode(mesh);
        model.setAnimationSpeed(1000);
        model.setMaterialFlag(MATERIAL_FLAG.EMF_LIGHTING, false);
        model.setFrameLoop(0, model.getEndFrame());
        modelName.setText(path);
        framerate.setValue(model.getAnimationSpeed());
        totalFramesCountLabel.setText("" + model.getEndFrame());
        startFrame.setValue(0);
        endFrame.setValue(model.getEndFrame());
        setEditorCurrentFrame((int) model.getFrameNr());
        //diffData = AnimationDiffRecounter.recount(model, 10000, device);
        //graph_diff_drawer.setData(diffData);
        //graph_diff_drawer.repaint();
    }

    private class AnimationSequencePanel extends JPanel {

        private DefaultTextField sequenceName;
        private DefaultFloatTextField sequenceFrameRate;
        private HSlider framesBoundary;
        private Color defaultColor = new Color(240, 240, 240);
        private Color selectedColor = new Color(200, 200, 200);
        private boolean panelSelected = false;
        private int expandedSize = 100;
        private int minimumSize = 50;

        public AnimationSequencePanel(int frameCount, float frameRate) {
            setOpaque(true);
            //setMaximumSize(new Dimension(400, 100));
            setPreferredSize(new Dimension(400, 50));
            setLayout(new MigLayout());
            add(new hLabel("Sequence name"));
            sequenceName = new DefaultTextField();
            add(sequenceName);
            sequenceName.setText("Track" + (int) (Math.random() * 999999));
            add(new hLabel("Animation speed"));
            sequenceFrameRate = new DefaultFloatTextField(0, 99999999);
            sequenceFrameRate.setValue(frameRate);
            add(sequenceFrameRate);
            LinkIconLabel deleteButton = new LinkIconLabel(null,
                    new InteractiveIcon(Wiring.getComponent(Utils.class).loadImageFromResources("icons/deleteSmall.png")));
            deleteButton.setToolTipText("Delete animation track");
            deleteButton.addClickEvent(new BooleanEvent() {
                @Override
                public boolean fireEvent(Object aThis) {
                    removeAnimationPanel();
                    return false;
                }
            });
            add(deleteButton, "wrap");
            framesBoundary = new HSlider();
            framesBoundary.setMinBoundary(0);
            framesBoundary.setMaxBoundary(frameCount);
            framesBoundary.setMaximum(frameCount);
            add(framesBoundary, "w 100%, spanx 10, wrap, ");
            initListeners();
            pack();
        }

        private void removeAnimationPanel() {
            selectedSequencePanel = null;
            sequences.remove(this);
            animSeqsPanel.remove(this);
            animSeqsPanel.revalidate();
            animSeqsPanel.validate();
            animSeqsPanel.repaint();
        }

        public void setAnimSeqCurrentFrame(int frame) {
            framesBoundary.setCurrent(frame);
        }

        public void setAnimSeqStartFrame(int frame) {
            framesBoundary.setMinBoundary(frame);
        }

        public int getAnimSeqStartFrame() {
            return framesBoundary.getMinBoundary();
        }

        public int getAnimSeqEndFrame() {
            return framesBoundary.getMaxBoundary();
        }

        public float getAnimSeqAnimationSpeed() {
            return (float) sequenceFrameRate.getValue();
        }

        public void setAnimSeqAnimationSpeed(float frameRate) {
            sequenceFrameRate.setValue(frameRate);
        }

        public void setAnimSeqEndFrame(int frame) {
            framesBoundary.setMaxBoundary(frame);
        }

        public String getTrackName() {
            return sequenceName.getText();
        }

        public void setTrackName(String trackName) {
            sequenceName.setText(trackName);
        }

        private void deselectPanel() {
            if (panelSelected) {
                panelSelected = false;
                setBackground(defaultColor);
                setPreferredSize(new Dimension(getPreferredSize().width, minimumSize));
            }
        }

        private void panelSelect() {
            if (!panelSelected) {
                selectedSequencePanel = this;
                for (AnimationSequencePanel panel : sequences) {
                    panel.deselectPanel();
                }

                panelSelected = true;
                setBackground(selectedColor);
                populateEditorFields();
                setPreferredSize(new Dimension(getPreferredSize().width, expandedSize));
                Container container = graph_diff_drawer.getParent();
                if (container != null) {
                    container.remove(graph_diff_drawer);
                }
                add(graph_diff_drawer, "wrap, grow, spanx 10");
                AnimationEditorWindow.this.revalidate();
                AnimationEditorWindow.this.validate();
                AnimationEditorWindow.this.repaint();
                AnimationEditorWindow.this.pack();
            }
        }

        private void populateEditorFields() {
            setEditorFrameRate((float) sequenceFrameRate.getValue());
            setEditorStartFrame(framesBoundary.getMinBoundary());
            setEditorEndFrame(framesBoundary.getMaxBoundary());
            setEditorCurrentFrame(framesBoundary.getMinBoundary());

            setFrameRateChanged((float) sequenceFrameRate.getValue());
            setCurrentFrame(framesBoundary.getMinBoundary());
            setStartFrame(framesBoundary.getMinBoundary());
            setEndFrame(framesBoundary.getMaxBoundary());

            animationPause();
        }

        private void initListeners() {
            sequenceFrameRate.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
                @Override
                public void valueChanged(Float value) {
                    panelSelect();
                    disableHandlers();
                    setFrameRateChanged(value);
                    setEditorFrameRate(value);

                    enableHandlers();
                }
            });

            framesBoundary.setOnChangeEvent(new HSlider.ChangeEvent() {
                @Override
                public void changeValue(SelectedMark mark, int value) {
                    panelSelect();
                    disableHandlers();
                    switch (mark) {
                        case Value:
                            setCurrentFrame(value);
                            setEditorCurrentFrame(value);
                            break;
                        case LeftBoundary:
                            setEditorStartFrame(value);
                            setStartFrame(value);
                            //if (getEditorCurrentFrame() < value) {
                            setCurrentFrame(value);
                            setEditorCurrentFrame(value);
                            //}
                            break;
                        case RightBoundary:
                            setEditorEndFrame(value);
                            setEndFrame(value);
                            if (getEditorCurrentFrame() > value) {
                                setCurrentFrame(value);
                                setEditorCurrentFrame(value);
                            }
                            break;
                    }

                    animationPause();
                    enableHandlers();
                }
            });
            MouseAdapter panelClickActivate = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    panelSelect();
                }
            };
            sequenceName.addMouseListener(panelClickActivate);
            addMouseListener(panelClickActivate);
            sequenceFrameRate.addMouseListener(panelClickActivate);
        }
    };
}